﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Southridge.Controls
{
    public class RatingsControl : Control
    {
        static RatingsControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RatingsControl), new FrameworkPropertyMetadata(typeof(RatingsControl)));
        }

        public RatingsControl()
        {
            this.GenerateStars();
        }

        #region Private Field

        ObservableCollection<RatingItem> _Stars;
        const string PART_StarListBoxName = "PART_StarListBoxtName";
        ListBox PART_StarListBox;

        #endregion

        #region RatingMaximum

        /// <summary>
        /// RatingMaximum Dependency Property
        /// </summary>
        public static readonly DependencyProperty RatingMaximumProperty =
            DependencyProperty.Register("RatingMaximum", typeof(int), typeof(RatingsControl),
                new FrameworkPropertyMetadata((int)5,
                    new PropertyChangedCallback(OnRatingMaximumChanged),
                    new CoerceValueCallback(CoerceRatingMaximumValue)));

        /// <summary>
        /// Gets or sets the RatingMaximum property.  This dependency property 
        /// indicates the highest possible rating.  This will also correspond to the total number of stars (or other indicator) that get displayed.
        /// </summary>
        public int RatingMaximum
        {
            get { return (int)GetValue(RatingMaximumProperty); }
            set { SetValue(RatingMaximumProperty, value); }
        }

        /// <summary>
        /// Handles changes to the RatingMaximum property.
        /// </summary>
        private static void OnRatingMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((RatingsControl)d).OnRatingMaximumChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the RatingMaximum property.
        /// </summary>
        protected virtual void OnRatingMaximumChanged(DependencyPropertyChangedEventArgs e)
        {
            GenerateStars();
        }

        /// <summary>
        /// Coerces the RatingMaximum value.
        /// </summary>
        private static object CoerceRatingMaximumValue(DependencyObject d, object value)
        {
            // this can't be less than zero
            return Math.Max((int)value, 0);
        }

        #endregion

        #region Rating

        /// <summary>
        /// Rating Dependency Property
        /// </summary>
        public static readonly DependencyProperty RatingProperty =
            DependencyProperty.Register("Rating", typeof(int), typeof(RatingsControl),
                new FrameworkPropertyMetadata((int)0,
                    new PropertyChangedCallback(OnRatingChanged),
                    new CoerceValueCallback(CoerceRatingValue)));

        /// <summary>
        /// Gets or sets the Rating property.  This dependency property 
        /// indicates the current rating.
        /// </summary>
        public int Rating
        {
            get { return (int)GetValue(RatingProperty); }
            set { SetValue(RatingProperty, value); }
        }

        /// <summary>
        /// Handles changes to the Rating property.
        /// </summary>
        private static void OnRatingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((RatingsControl)d).OnRatingChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the Rating property.
        /// </summary>
        protected virtual void OnRatingChanged(DependencyPropertyChangedEventArgs e)
        {
            UpdateStarsRating();
        }

        /// <summary>
        /// Coerces the Rating value.
        /// </summary>
        private static object CoerceRatingValue(DependencyObject d, object value)
        {
            return Math.Max(0, (int) value);
        }

        #endregion

        #region StarTemplate

        /// <summary>
        /// StarTemplate Dependency Property
        /// </summary>
        public static readonly DependencyProperty StarTemplateProperty =
            DependencyProperty.Register("StarTemplate", typeof(DataTemplate), typeof(RatingsControl),
                new FrameworkPropertyMetadata((DataTemplate)null,
                    new PropertyChangedCallback(OnStarTemplateChanged)));

        /// <summary>
        /// Gets or sets the StarTemplate property.  This dependency property 
        /// indicates the template used for each of the stars in the ratings 
        /// control.  It should be of type DataTemplate and the templated items 
        /// will be of type RatingItem.
        /// </summary>
        public DataTemplate StarTemplate
        {
            get { return (DataTemplate)GetValue(StarTemplateProperty); }
            set { SetValue(StarTemplateProperty, value); }
        }

        /// <summary>
        /// Handles changes to the StarTemplate property.
        /// </summary>
        private static void OnStarTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((RatingsControl)d).OnStarTemplateChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the StarTemplate property.
        /// </summary>
        protected virtual void OnStarTemplateChanged(DependencyPropertyChangedEventArgs e)
        {
            //if (PART_StarListBox != null) PART_StarListBox.ItemTemplate = (e.NewValue as DataTemplate);
        }

        #endregion


        #region Overrides

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // find our template parts
            PART_StarListBox = this.Template.FindName(PART_StarListBoxName, this) as ListBox;
            PART_StarListBox.ItemsSource = _Stars;
            PART_StarListBox.SelectionChanged += new SelectionChangedEventHandler(PART_StarListBox_SelectionChanged);
        }

        void PART_StarListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            this.Rating = PART_StarListBox.SelectedIndex + 1;
        }

        #endregion

        #region Private Methods

        private void GenerateStars()
        {
            // clear the current collection
            if (_Stars == null) _Stars = new ObservableCollection<RatingItem>();
            _Stars.Clear();

            // generate the new items
            for (int i = 0; i < this.RatingMaximum; i++)
            {
                this._Stars.Add(new RatingItem());
            }

            UpdateStarsRating();
        }

        private void UpdateStarsRating()
        {
            for (int i = 0; i < _Stars.Count; i++)
            {
                if (i >= this.Rating)
                {
                    _Stars[i].State = RatingState.NotSelected;
                }
                else
                {
                    _Stars[i].State = RatingState.Selected;
                }
            }
        }

        #endregion
    }

    public class RatingItem : INotifyPropertyChanged
    {
        private RatingState _state = RatingState.NotSelected;
        public RatingState State
        {
            get { return _state; }
            set
            {
                _state = value;
                RaisePropertyChanged("State");
            }
        }
        
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion

    }

    public enum RatingState
    {
        Selected,
        HalfSelected, // not implemented
        NotSelected
    }

    
}
